Créer des chaînes de traitements reproductibles avec R (Damien Dotta)
18/11/2024
Plusieurs objectifs :
- targets s’applique aux projets avec une forte contrainte de reproductibilité
- La chaîne de traitement doit être exclusivement codée avec R
- Ce package se justifie sur les projets avec une certaine ampleur
Une chaîne de traitement (ou un pipeline en anglais) est constituée par l’ensemble des étapes qui s’y déroulent.
Un exemple simple serait :
#' @name agregations
#'
#' @param base nom de la base sur laquelle on agrege les donnees
#'
#' @return une base agregee
agregations <- function(base) {
base |>
group_by(Species) |>
summarise(
moyenne_petales = mean(Petal.Length, na.rm = TRUE),
moyenne_sepales = mean(Sepal.Length, na.rm = TRUE)
)
}
mes_resultats <- mes_traitements |>
agregations()#' @name faire_graphiques
#'
#' @param base nom de la base sur laquelle on realise le graphique
#' @param partie partie de la fleur sur laquelle realiser le graphique
#'
#' @return un graphique ggplot2
faire_graphiques <- function(base, partie) {
ggplot(data = base) +
geom_jitter(aes(x = partie, y = Species, color=Species),
width=0.05, height=0.1, alpha=0.5)
}
mes_resultats |>
faire_graphiques()_targets.R (1/2)Le package impose une seule chose :
Créer un fichier _targets.R à la racine de votre projet
Ce fichier contient la description de l’ensemble des étapes de votre chaîne de traitement.
Voici comment est structuré mon projet R mais aucune organisation n’est imposée par le package.
├── donnees
│ └── iris.csv
├── R
│ ├── agregations.R
│ └── faire_graphiques.R
└── _targets.R
_targets.R (2/2)La chaîne de traitement (soit le fichier _targets.R) est représentée par une liste de tar_target(), soit les objets R qui sont les cibles intermédiaires de l’analyse.
Ils sont le résultat de l’application à une cible précédente d’une fonction pour obtenir la cible suivante.
tar_target() est le nom de la cible (que vous choisissez - un nom de cible doit être unique)._targets.RPour lancer l’exécution de la chaîne de traitement, on utiliser la fonction tar_make().
Lorsqu’on utilise tar_make(), targets :
targets ?
A partir d’une chaîne de traitement, ce package permet de sauvegarder automatiquement dans un cache les résultats intermédiaires (appelés “targets” ou cibles) :
> tar_make()
▶ dispatched target fichier_donnees
● completed target fichier_donnees [0.42 seconds]
▶ dispatched target mes_donnees
● completed target mes_donnees [0.21 seconds]
▶ dispatched target mes_traitements
● completed target mes_traitements [0 seconds]
▶ dispatched target mes_resultats
● completed target mes_resultats [0.02 seconds]
▶ dispatched target mon_graphique
● completed target mon_graphique [0.02 seconds]
▶ ended pipeline [0.96 seconds]
Lorsque la chaîne de traitement est modeste (comme dans notre exemple), on peut la visualiser avec la fonction tar_visnetwork().
Cette fonction affiche sous forme de diagramme la structure de notre pipeline :
Comment interpréter :
> tar_load(mes_donnees)
> mes_donnees
# A tibble: 150 × 5
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<dbl> <dbl> <dbl> <dbl> <chr>
1 51 35 14 2 setosa
2 49 3 14 2 setosa
3 47 32 13 2 setosa
4 46 31 15 2 setosa
5 5 36 14 2 setosa
6 54 39 17 4 setosa
7 46 34 14 3 setosa
8 5 34 15 2 setosa
9 44 29 14 2 setosa
10 49 31 15 1 setosa
# ℹ 140 more rows
# ℹ Use `print(n = ...)` to see more rowsRelançons notre pipeline sans rien modifier :
> tar_make()
✔ skipped target fichier_donnees
✔ skipped target mes_donnees
✔ skipped target mes_traitements
✔ skipped target mes_resultats
✔ skipped target mon_graphique
✔ skipped pipeline [0.18 seconds]
=> Toutes les cibles ont été “skippées” car quand on lance tar_make(), seules les cibles qui sont à l’état “outdated” sont recalculées.
Modifions un caractère esthétique de notre graphique (en réduisant l’opacité de notre geom - dans le code, il s’agit de l’argument alpha).
Avant de relancer notre pipeline, visualisons le diagramme :
=> Grâce à sa gestion interne des dépendances entre les cibles, targets s’est rendu compte que la faire_graphique a été modifiée (elle est passée en statut “outdated”). Et comme la cible mon_graphique dépend de cette fonction, celle-ci a également été placée en outdated.
Note
On peut directement obtenir la liste des cibles qui ne sont plus à jour à l’aide de la fonction tar_outdated().
Relançons notre pipeline :
> tar_make()
✔ skipped target fichier_donnees
✔ skipped target mes_donnees
✔ skipped target mes_traitements
✔ skipped target mes_resultats
▶ dispatched target mon_graphique
● completed target mon_graphique [0 seconds]
▶ ended pipeline [0.57 seconds]
=> On voit que les cibles fichier_donnees, mes_donnees, mes_traitements et mes_resultats ont été ignorées : targets est allé prendre directement leurs valeurs déjà stockées en cache.
Par contre, la cible mon_graphique a bien été recalculée.
On peut désormais vérifier que notre pipeline est à jour en relançant tar_visnetwork() :
Prenons maintenant le cas où les données en entrée changent.
Que se passerait-il ?
Par défaut, les cibles sont stockées au format rds qui est un format lent lorsqu’on a atteint une certaine volumétrie de données.
=> Il est conseillé d’utiliser un autre format de stockage des cibles.
Par exemple pour les dataframes on pourrait rajouter dans notre pipeline :
=> Dans le dossier _targets/object, le fichier sera stocké au format exigé.
targets garde une copie des objets correspondant aux cibles du pipeline dans un cache, en fait sous forme de fichiers placés dans un sous-dossier _targets.
On a vu qu’on peut récupérer ces objets dans notre session via les fonctions tar_read() et tar_load().
targets propose également plusieurs fonctions pour gérer les données et métadonnées en cache :
Le fichier _targets.R fournit une description détaillée des étapes du projet. Cela facilite les choses quand on revient dessus après un certain temps et qu’on n’a plus tous les détails en tête, ou si on le partage avec un collègue.
Chaque cible du pipeline est très majoritairement définie via des fonctions, ce qui garantit une séparation et une encapsulation des différentes étapes.
l’utilisation de tar_make() garantit que toutes les cibles du pipeline sont recalculées dans le bon ordre : pas de risque de lancer un script sur des données qui ne seraient pas complètement à jour parce qu’on a oublié de relancer certains recodages par exemple.
tar_make() s’exécute toujours dans un environnement vide, ce qui élimine les problèmes liés à l’état de notre session en cours et garantit la reproductibilité des résultats.
Comme targets conserve une copie des résultats des cibles en cache, pas besoin de tout recalculer quand on relance le projet, on peut récupérer *µµdirectement les résultats et savoir s’ils sont à jour.
tar_make() ne recalcule que les cibles qui le nécessitent, les temps de calcul et d’exécution sont optimisés.
Malgré les outils mis à disposition par le mainteneur du package, le débuggage est un peu plus complexe avec targets.
Le package contient de nombreuses fonctionnalités avancées (notions de branches, parallélisation des calculs, articulation avec renv) ce qui peut rendre le code écrit avec targets moins facilement lisible.